use crate::{Kind, Status};
use alloc::vec::Vec;
use core::convert::{AsMut, TryInto};
use unmp_id::Id;

fn clone_into_array<A, T>(slice: &[T]) -> A
where
    A: Default + AsMut<[T]>,
    T: Clone,
{
    let mut a = Default::default();
    <A as AsMut<[T]>>::as_mut(&mut a).clone_from_slice(slice);
    a
}

#[derive(Debug, Clone, Copy)]
pub enum CmdType {
    Request,
    Respose,
    Notice,
    Unknow,
}

#[derive(Debug, Clone)]
pub enum Cmd {
    /// 注册设备ID
    GetIdReq {
        product_id: u64,
        product_code: [u8; 8],
    },
    GetIdRes {
        device_id: Id,
        private_key: [u8; 8],
        public_key: [u8; 8],
    },
    /// 设备上线
    LoginReq {
        code: [u8; 8],
    },
    LoginRes {},
    /// 心跳
    HeartReq {},
    HeartRes {},
    /// 获取时间戳
    GetTimestampReq {},
    GetTimestampRes {
        timestamp: u64,
    },
    /// 设置云端变量实际值
    SetActualReq {
        id: Id,
        code: [u8; 8],
        k: u16,
        v: Vec<u8>,
    },
    SetActualRes {
        id: Id,
        k: u16,
        v: Vec<u8>,
    },
    /// 读取云端变量实际值
    GetActualReq {
        id: Id,
        code: [u8; 8],
        k: u16,
    },
    GetActualRes {
        id: Id,
        k: u16,
        v: Vec<u8>,
    },
    /// 订阅云端变量实际值变更通知
    SubActualReq {
        id: Id,
        code: [u8; 8],
        k: u16,
    },
    SubActualRes {
        id: Id,
        k: u16,
    },
    /// 订阅全部云端变量实际值变更通知
    SubAllActualReq {
        id: Id,
        code: [u8; 8],
    },
    SubAllActualRes {
        id: Id,
    },
    /// 发送云端变量实际值变更通知
    NoticeActual {
        id: Id,
        k: u16,
        v: Vec<u8>,
    },
    /// 设置云端变量目标值
    SetTargetReq {
        id: Id,
        code: [u8; 8],
        k: u16,
        v: Vec<u8>,
    },
    SetTargetRes {
        id: Id,
        k: u16,
        v: Vec<u8>,
    },
    /// 读取云端变量目标值
    GetTargetReq {
        id: Id,
        code: [u8; 8],
        k: u16,
    },
    GetTargetRes {
        id: Id,
        k: u16,
        v: Vec<u8>,
    },
    /// 订阅云端变量目标值变更通知
    SubTargetReq {
        id: Id,
        code: [u8; 8],
        k: u16,
    },
    SubTargetRes {
        id: Id,
        k: u16,
    },
    /// 订阅全部云端变量目标值变更通知
    SubAllTargetReq {
        id: Id,
        code: [u8; 8],
    },
    SubAllTargetRes {
        id: Id,
    },
    /// 发送云端变量目标值变更通知
    NoticeTarget {
        id: Id,
        k: u16,
        v: Vec<u8>,
    },
    /// 进入绑定模式
    EnterBindReq {
        code: [u8; 4],
    },
    EnterBindRes {
        code: [u8; 4],
    },
    /// 用户绑定设备
    BindReq {
        product_id: u64,
        code: [u8; 4],
    },
    BindRes {
        id: Id,
        public_key: [u8; 8],
    },
    /// 用户解绑设备
    UnbindReq {
        id: Id,
    },
    UnbindRes {
        id: Id,
    },
    /// 发送设备被绑定通知
    NoticeBind {
        id: Id,
        public_key: [u8; 8],
    },
    /// 发送设备被解绑通知
    NoticeUnbind {
        id: Id,
    },
    /// 获取软件包信息
    GetOTAInfo,
    /// 获取软件包分片
    GetOTA,
    /// 进入升级模式
    EnterOTA,
    /// 无效指令
    Unknown {
        body: Vec<u8>,
    },
}
impl Default for Cmd {
    fn default() -> Self {
        Cmd::Unknown { body: Vec::new() }
    }
}
impl Cmd {
    /// 是否为响应指令
    pub fn cmd_type(&self) -> CmdType {
        match self {
            &Cmd::GetIdReq { .. }
            | &Cmd::LoginReq { .. }
            | &Cmd::HeartReq { .. }
            | &Cmd::GetTimestampReq { .. }
            | &Cmd::SetActualReq { .. }
            | &Cmd::GetActualReq { .. }
            | &Cmd::SubActualReq { .. }
            | &Cmd::SubAllActualReq { .. }
            | &Self::SetTargetReq { .. }
            | &Cmd::GetTargetReq { .. }
            | &Cmd::SubTargetReq { .. }
            | &Cmd::SubAllTargetReq { .. }
            | &Cmd::EnterBindReq { .. }
            | &Cmd::BindReq { .. }
            | &Cmd::UnbindReq { .. } => CmdType::Request,
            &Cmd::GetIdRes { .. }
            | &Cmd::LoginRes { .. }
            | &Cmd::HeartRes { .. }
            | &Cmd::GetTimestampRes { .. }
            | &Cmd::SetActualRes { .. }
            | &Cmd::GetActualRes { .. }
            | &Cmd::SubActualRes { .. }
            | &Cmd::SubAllActualRes { .. }
            | &Self::SetTargetRes { .. }
            | &Cmd::GetTargetRes { .. }
            | &Cmd::SubTargetRes { .. }
            | &Cmd::SubAllTargetRes { .. }
            | &Cmd::EnterBindRes { .. }
            | &Cmd::BindRes { .. }
            | &Cmd::UnbindRes { .. } => CmdType::Respose,
            &Cmd::NoticeActual { .. }
            | &Cmd::NoticeTarget { .. }
            | &Cmd::NoticeBind { .. }
            | &Cmd::NoticeUnbind { .. } => CmdType::Notice,
            _ => CmdType::Unknow,
        }
    }
    pub fn parse(kind: Kind, status: Status, buf: &[u8]) -> Self {
        match (kind, status) {
            (Kind::GetId, Status::Request) => {
                let (id, rest) = buf.split_at(8);
                let (code, _) = rest.split_at(8);
                Cmd::GetIdReq {
                    product_id: u64::from_be_bytes(id.try_into().unwrap()),
                    product_code: clone_into_array(code),
                }
            }
            (Kind::GetId, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (private_key, rest) = rest.split_at(8);
                let (public_key, _) = rest.split_at(8);
                Cmd::GetIdRes {
                    device_id: Id::from(id),
                    private_key: clone_into_array(private_key),
                    public_key: clone_into_array(public_key),
                }
            }
            (Kind::Login, Status::Request) => Cmd::LoginReq {
                code: clone_into_array(&buf[0..8]),
            },
            (Kind::Login, Status::Response) => Cmd::LoginRes {},
            (Kind::Heart, Status::Request) => Cmd::HeartReq {},
            (Kind::Heart, Status::Response) => Cmd::HeartRes {},
            (Kind::GetTimestamp, Status::Request) => Cmd::GetTimestampReq {},
            (Kind::GetTimestamp, Status::Response) => Cmd::GetTimestampRes {
                timestamp: u64::from_be_bytes((&buf[0..8]).try_into().unwrap()),
            },
            (Kind::SetActual, Status::Request) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (public_key, rest) = rest.split_at(8);
                let (k, rest) = rest.split_at(2);
                let (v, _) = rest[1..].split_at(rest[0] as _);
                Cmd::SetActualReq {
                    id: Id::from(id),
                    code: clone_into_array(public_key),
                    k: u16::from_be_bytes((k).try_into().unwrap()),
                    v: Vec::from(v),
                }
            }
            (Kind::SetActual, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (k, rest) = rest.split_at(2);
                let (v, _) = rest[1..].split_at(rest[0] as _);
                Cmd::SetActualRes {
                    id: Id::from(id),
                    k: u16::from_be_bytes((k).try_into().unwrap()),
                    v: Vec::from(v),
                }
            }
            (Kind::GetActual, Status::Request) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (public_key, rest) = rest.split_at(8);
                let (k, _) = rest.split_at(2);
                Cmd::GetActualReq {
                    id: Id::from(id),
                    code: clone_into_array(public_key),
                    k: u16::from_be_bytes((k).try_into().unwrap()),
                }
            }
            (Kind::GetActual, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (k, rest) = rest.split_at(2);
                let (v, _) = rest[1..].split_at(rest[0] as _);
                Cmd::GetActualRes {
                    id: Id::from(id),
                    k: u16::from_be_bytes((k).try_into().unwrap()),
                    v: Vec::from(v),
                }
            }
            (Kind::SubActual, Status::Request) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (public_key, rest) = rest.split_at(8);
                let (k, _) = rest.split_at(2);
                Cmd::SubActualReq {
                    id: Id::from(id),
                    code: clone_into_array(public_key),
                    k: u16::from_be_bytes(k.try_into().unwrap()),
                }
            }
            (Kind::SubActual, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (k, _) = rest.split_at(2);
                Cmd::SubActualRes {
                    id: Id::from(id),
                    k: u16::from_be_bytes(k.try_into().unwrap()),
                }
            }
            (Kind::SubAllActual, Status::Request) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (code, _) = rest.split_at(8);
                Cmd::SubAllActualReq {
                    id: Id::from(id),
                    code: clone_into_array(code),
                }
            }
            (Kind::SubAllActual, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, _) = buf.split_at(id_len);
                Cmd::SubAllActualRes { id: Id::from(id) }
            }
            (Kind::NoticeActual, Status::Notice) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (k, rest) = rest.split_at(2);
                let (v, _) = rest[1..].split_at(rest[0] as _);
                Cmd::NoticeActual {
                    id: Id::from(id),
                    k: u16::from_be_bytes(k.try_into().unwrap()),
                    v: Vec::from(v),
                }
            }
            (Kind::SetTarget, Status::Request) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (public_key, rest) = rest.split_at(8);
                let (k, rest) = rest.split_at(2);
                let (v, _) = rest[1..].split_at(rest[0] as _);
                Cmd::SetTargetReq {
                    id: Id::from(id),
                    code: clone_into_array(public_key),
                    k: u16::from_be_bytes((k).try_into().unwrap()),
                    v: Vec::from(v),
                }
            }
            (Kind::SetTarget, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (k, rest) = rest.split_at(2);
                let (v, _) = rest[1..].split_at(rest[0] as _);
                Cmd::SetTargetRes {
                    id: Id::from(id),
                    k: u16::from_be_bytes((k).try_into().unwrap()),
                    v: Vec::from(v),
                }
            }
            (Kind::GetTarget, Status::Request) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (public_key, rest) = rest.split_at(8);
                let (k, _) = rest.split_at(2);
                Cmd::GetTargetReq {
                    id: Id::from(id),
                    code: clone_into_array(public_key),
                    k: u16::from_be_bytes((k).try_into().unwrap()),
                }
            }
            (Kind::GetTarget, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (k, rest) = rest.split_at(2);
                let (v, _) = rest[1..].split_at(rest[0] as _);
                Cmd::GetTargetRes {
                    id: Id::from(id),
                    k: u16::from_be_bytes((k).try_into().unwrap()),
                    v: Vec::from(v),
                }
            }
            (Kind::SubTarget, Status::Request) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (public_key, rest) = rest.split_at(8);
                let (k, _) = rest.split_at(2);
                Cmd::SubTargetReq {
                    id: Id::from(id),
                    code: clone_into_array(public_key),
                    k: u16::from_be_bytes(k.try_into().unwrap()),
                }
            }
            (Kind::SubTarget, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (k, _) = rest.split_at(2);
                Cmd::SubTargetRes {
                    id: Id::from(id),
                    k: u16::from_be_bytes(k.try_into().unwrap()),
                }
            }
            (Kind::SubAllTarget, Status::Request) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (code, _) = rest.split_at(8);
                Cmd::SubAllTargetReq {
                    id: Id::from(id),
                    code: clone_into_array(code),
                }
            }
            (Kind::SubAllTarget, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, _) = buf.split_at(id_len);
                Cmd::SubAllTargetRes { id: Id::from(id) }
            }
            (Kind::NoticeTarget, Status::Notice) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (k, rest) = rest.split_at(2);
                let (v, _) = rest[1..].split_at(rest[0] as _);
                Cmd::NoticeTarget {
                    id: Id::from(id),
                    k: u16::from_be_bytes(k.try_into().unwrap()),
                    v: Vec::from(v),
                }
            }
            (Kind::EnterBind, Status::Request) => {
                let (code, _) = buf.split_at(4);
                Cmd::EnterBindReq {
                    code: clone_into_array(code),
                }
            }
            (Kind::EnterBind, Status::Response) => {
                let (code, _) = buf.split_at(4);
                Cmd::EnterBindRes {
                    code: clone_into_array(code),
                }
            }
            (Kind::Bind, Status::Request) => {
                let (product_id, rest) = buf.split_at(8);
                let (code, _) = rest.split_at(4);
                Cmd::BindReq {
                    product_id: u64::from_be_bytes(product_id.try_into().unwrap()),
                    code: clone_into_array(code),
                }
            }
            (Kind::Bind, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (key, _) = rest.split_at(8);
                Cmd::BindRes {
                    id: Id::from(id),
                    public_key: clone_into_array(key),
                }
            }
            (Kind::Unbind, Status::Request) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, _) = buf.split_at(id_len);
                Cmd::UnbindReq { id: Id::from(id) }
            }
            (Kind::Unbind, Status::Response) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, _) = buf.split_at(id_len);
                Cmd::UnbindRes { id: Id::from(id) }
            }
            (Kind::NoticeBind, Status::Notice) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, rest) = buf.split_at(id_len);
                let (key, _) = rest.split_at(8);
                Cmd::NoticeBind {
                    id: Id::from(id),
                    public_key: clone_into_array(key),
                }
            }
            (Kind::NoticeUnbind, Status::Notice) => {
                let id_len = (buf[0] & 0x0F) as usize + 1;
                let (id, _) = buf.split_at(id_len);
                Cmd::NoticeUnbind { id: Id::from(id) }
            }
            _ => Cmd::Unknown {
                body: Vec::from(buf),
            },
        }
    }
    pub fn to_buf(&self) -> Vec<u8> {
        match self {
            Cmd::GetIdReq {
                product_id,
                product_code,
            } => {
                let mut buf = Vec::from(product_id.to_be_bytes());
                buf.extend_from_slice(product_code);
                buf
            }
            Cmd::GetIdRes {
                device_id,
                private_key,
                public_key,
            } => {
                let mut buf = Vec::from(&device_id as &[u8]);
                buf.extend_from_slice(private_key);
                buf.extend_from_slice(public_key);
                buf
            }
            Cmd::LoginReq { code } => Vec::from(code as &[u8]),
            Cmd::LoginRes {} => Vec::new(),
            Cmd::HeartReq {} => Vec::new(),
            Cmd::HeartRes {} => Vec::new(),
            Cmd::GetTimestampReq {} => Vec::new(),
            Cmd::GetTimestampRes { timestamp } => Vec::from(timestamp.to_be_bytes()),
            Cmd::SetActualReq { id, code, k, v } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(code);
                buf.extend_from_slice(&k.to_be_bytes());
                buf.push(v.len() as u8);
                buf.extend_from_slice(&v);
                buf
            }
            Cmd::SetActualRes { id, k, v } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(&k.to_be_bytes());
                buf.push(v.len() as u8);
                buf.extend_from_slice(&v);
                buf
            }
            Cmd::GetActualReq { id, code, k } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(code);
                buf.extend_from_slice(&k.to_be_bytes());
                buf
            }
            Cmd::GetActualRes { id, k, v } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(&k.to_be_bytes());
                buf.push(v.len() as u8);
                buf.extend_from_slice(&v);
                buf
            }
            Cmd::SubActualReq { id, code, k } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(code);
                buf.extend_from_slice(&k.to_be_bytes());
                buf
            }
            Cmd::SubActualRes { id, k } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(&k.to_be_bytes());
                buf
            }
            Cmd::SubAllActualReq { id, code } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(code);
                buf
            }
            Cmd::SubAllActualRes { id } => Vec::from(&id as &[u8]),
            Cmd::NoticeActual { id, k, v } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(&k.to_be_bytes());
                buf.push(v.len() as u8);
                buf.extend_from_slice(&v);
                buf
            }
            Cmd::SetTargetReq { id, code, k, v } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(code);
                buf.extend_from_slice(&k.to_be_bytes());
                buf.push(v.len() as u8);
                buf.extend_from_slice(&v);
                buf
            }
            Cmd::SetTargetRes { id, k, v } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(&k.to_be_bytes());
                buf.push(v.len() as u8);
                buf.extend_from_slice(&v);
                buf
            }
            Cmd::GetTargetReq { id, code, k } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(code);
                buf.extend_from_slice(&k.to_be_bytes());
                buf
            }
            Cmd::GetTargetRes { id, k, v } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(&k.to_be_bytes());
                buf.push(v.len() as u8);
                buf.extend_from_slice(&v);
                buf
            }
            Cmd::SubTargetReq { id, code, k } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(code);
                buf.extend_from_slice(&k.to_be_bytes());
                buf
            }
            Cmd::SubTargetRes { id, k } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(&k.to_be_bytes());
                buf
            }
            Cmd::SubAllTargetReq { id, code } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(code);
                buf
            }
            Cmd::SubAllTargetRes { id } => Vec::from(&id as &[u8]),
            Cmd::NoticeTarget { id, k, v } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(&k.to_be_bytes());
                buf.push(v.len() as u8);
                buf.extend_from_slice(&v);
                buf
            }
            Cmd::EnterBindReq { code } => Vec::from(code as &[u8]),
            Cmd::EnterBindRes { code } => Vec::from(code as &[u8]),
            Cmd::BindReq { product_id, code } => {
                let mut buf = Vec::from(product_id.to_be_bytes());
                buf.extend_from_slice(code);
                buf
            }
            Cmd::BindRes { id, public_key } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(public_key);
                buf
            }
            Cmd::UnbindReq { id } => Vec::from(&id as &[u8]),
            Cmd::UnbindRes { id } => Vec::from(&id as &[u8]),
            Cmd::NoticeBind { id, public_key } => {
                let mut buf = Vec::from(&id as &[u8]);
                buf.extend_from_slice(public_key);
                buf
            }
            Cmd::NoticeUnbind { id } => Vec::from(&id as &[u8]),
            Cmd::Unknown { body } => body.clone(),
            _ => Vec::new(),
        }
    }
}
